#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "log.h"
#include "osal.h"
#include <api/libcurl/rbuffer.h>

#ifdef _OLD_HLS_SUPPORT_

#ifdef __linux
#include <stdint.h>
#include <arpa/inet.h>
#include <curl/curl.h>
#include "ali_hls.h"
#else
#include <api/libcurl/curl.h>
#include <api/libalihls/ali_hls.h>
#endif

#define LOG(FMT, ARGS...) __log("HLS", FMT, ##ARGS)

// for debug
//#define IS_RECORD_FILE

#define HTTP_COMMON_LINE_LENGTH 1024
#define HLS_M3U8FILE_MAX_LENGTH     (1024 * 512)
#define HLS_NETWORK_CONN_TIMEOUT	5
#define HLS_NETWORK_TIMEOUT		30
#define HLS_PROBE_BUF_SIZE	1024
#define HLS_RING_BUF_SIZE	(1024 * 512)
#define HLS_AES_BLOCK_SIZE		16
#define HLS_DEC_BUF_SIZE		(1024 * 64)//64k
#define SPACE_CHARS	" \t\r\n"
#define ATTR_ALIGN_32        __attribute__((aligned(64)))

typedef struct {
	int pos;
	int key_pos;
	int seq_no;
	double duration;
} segment;
typedef struct{
    int url_pos;
    int bandwidth;
}variants;

struct HLSContext {
	CURL *curl;
    CURL *curl_ts;//jary test
	THREAD tid_m3u8;//m3u parse thread
	THREAD tid_ts;//ts download thread
	uint8_t tid_m3u8_exit;
	uint8_t tid_ts_exit;
	rbuffer rb;
    
	int start_seq_no;
	int download_seq_no;
	int target_duration;
	int is_continue;

	int cur_pos;
	int is_segment;
	char *m3u8_stream;

#ifdef DSC_SUPPORT
	int is_encryped;
	int has_ivec;
	void *dec_handle;
	int cur_key_pos;
	ATTR_ALIGN_32
	uint8_t dec_buf[HLS_DEC_BUF_SIZE];
	ATTR_ALIGN_32
	uint8_t enc_buf[HLS_DEC_BUF_SIZE];
	uint32_t dec_len;
	uint32_t enc_len;
	uint32_t cur_size;// download size of the current segment
	uint8_t key[16];// current key
	uint8_t ivec[16];// current initialize vector
#endif

#ifdef IS_RECORD_FILE
	FILE *stream;
#endif

	segment *segments;
	int segment_nr;
	int segment_idx;
	int seek_idx;
	double duration;

    variants *variants;
    int variant_nr;

    MUTEX lock;
	int is_do_seek;
    int is_new_seqno;
    int exit_flag;
};

typedef struct {
	char *buf;
	uint32_t size;
	uint32_t offset;// actual size
} COMMON_DOWNLOAD_CONTEXT;


static HLSContext *m_context = NULL;
static int m_is_hls = 0;

static int hls_m3u8_eof = 0;
static int user_choice = 0xFFFFFFFF;

// for QT demo test
static long s_cur_pos = 0;
void __hls_seek_forward(long time)
{
	if (NULL != m_context) {
		long duration = hls_stream_duration();
		s_cur_pos += time;
		s_cur_pos = s_cur_pos < duration ? s_cur_pos : duration;
		hls_stream_seek(m_context, s_cur_pos);
	}
}

void __hls_seek_backward(long time)
{
	if (NULL != m_context) {
		s_cur_pos -= time;
		s_cur_pos = s_cur_pos > 0 ? s_cur_pos : 0;
		hls_stream_seek(m_context, s_cur_pos);
	}
}

static inline int av_toupper(int c)
{
	if (c >= 'a' && c <= 'z')
		c ^= 0x20;
	return c;
}

static int ff_hex_to_data(uint8_t *data, const char *p)
{
	int c, len, v;

	len = 0;
	v = 1;
	for (;;) {
		p += strspn(p, SPACE_CHARS);
		if (*p == '\0')
			break;
		c = av_toupper((unsigned char) *p++);
		if (c >= '0' && c <= '9')
			c = c - '0';
		else if (c >= 'A' && c <= 'F')
			c = c - 'A' + 10;
		else
			break;        
		v = (v << 4) | c;
		if (v & 0x100) {
			if (data)
				data[len] = v;
			len++;
			v = 1;
		}
	}
	return len;
}

static int av_strstart(const char *str, const char *pfx, const char **ptr)
{
	while (*pfx && *pfx == *str) {
		pfx++;
		str++;
	}
	if (!*pfx && ptr)
		*ptr = str;
	return !*pfx;
}

static size_t av_strlcpy(char *dst, const char *src, size_t size)
{
	size_t len = 0;
	while (++len < size && *src)
		*dst++ = *src++;
	if (len <= size)
		*dst = 0;
	return len + strlen(src) - 1;
}

static size_t av_strlcat(char *dst, const char *src, size_t size)
{
	size_t len = strlen(dst);
	if (size <= len + 1)
		return len + strlen(src);
	return len + av_strlcpy(dst + len, src, size - len);
}

static void ff_make_absolute_url(char *buf, int size, const char *base,
				 const char *rel)
{
	char *sep, *path_query;
	/* Absolute path, relative to the current server */
	if (base && strstr(base, "://") && rel[0] == '/') {
		if (base != buf)
			av_strlcpy(buf, base, size);
		sep = strstr(buf, "://");
		if (sep) {
			/* Take scheme from base url */
			if (rel[1] == '/') {
				sep[1] = '\0';
			} else {
				/* Take scheme and host from base url */
				sep += 3;
				sep = strchr(sep, '/');
				if (sep)
					*sep = '\0';
			}
		}
		av_strlcat(buf, rel, size);
		return;
	}
	/* If rel actually is an absolute url, just copy it */
	if (!base || strstr(rel, "://") || rel[0] == '/') {
		av_strlcpy(buf, rel, size);
		return;
	}
	if (base != buf)
		av_strlcpy(buf, base, size);

	/* Strip off any query string from base */
	path_query = strchr(buf, '?');
	if (path_query != NULL)
		*path_query = '\0';

	/* Is relative path just a new query part? */
	if (rel[0] == '?') {
		av_strlcat(buf, rel, size);
		return;
	}

	/* Remove the file name from the base url */
	sep = strrchr(buf, '/');
	if (sep)
		sep[1] = '\0';
	else
		buf[0] = '\0';
	while (av_strstart(rel, "../", NULL) && sep) {
		/* Remove the path delimiter at the end */
		sep[0] = '\0';
		sep = strrchr(buf, '/');
		/* If the next directory name to pop off is "..", break here */
		if (!strcmp(sep ? &sep[1] : buf, "..")) {
			/* Readd the slash we just removed */
			av_strlcat(buf, "/", size);
			break;
		}
		/* Cut off the directory name */
		if (sep)
			sep[1] = '\0';
		else
			buf[0] = '\0';
		rel += 3;
	}
	av_strlcat(buf, rel, size);
}

//!
int hls_init_para_setting(int *m3u8file_max_len, int *line_max_len)
{
    if(m3u8file_max_len)
    {
        
    }
    if(line_max_len)
    {

    }
}

static int ring_buffer_read_write_complete(HLSContext *context, uint8_t *buf, uint32_t size,
					   int (*read_write)(rbuffer *, char *, int))
{
    uint32_t completed = 0;
    uint32_t retval = 0;

    while (completed < size)
    {
        if(context->exit_flag)
        {
            break;            
        }
            
        retval = read_write(&context->rb, buf + completed, size - completed);
        if (0 == retval) 
        {
            if(0 != (size - completed) && read_write == rbuffer_write)
            {
                osal_task_sleep(10);
            }
            else
            {
                break;
            }
        }
        completed += retval;
    }
    
    return completed;
}

static int ring_buffer_read_complete(HLSContext *context, uint8_t *buf, int size)
{
    int ret = 0;
    //LOG("\n read size: %d\n", size);
    ret = ring_buffer_read_write_complete(context, buf, size, rbuffer_read);
    //LOG("read size done: %d\n", ret);
    return ret;
}

static int ring_buffer_write_complete(HLSContext *context, uint8_t *buf, int size)
{
    int ret = 0;
    //LOG("write size: %d\n", size);
    ret = ring_buffer_read_write_complete(context, buf, size, rbuffer_write);
    //LOG("write size done: %d\n", ret);
    return ret;
}


static int curl_progress_cb(void *clientp,
                                      double dltotal,
                                      double dlnow,
                                      double ultotal,
                                      double ulnow)
{
    return hls_m3u8_eof;
}

static size_t common_download_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
	COMMON_DOWNLOAD_CONTEXT *context = (COMMON_DOWNLOAD_CONTEXT *)userdata;
	int copy_size = size * nmemb;
	int left = context->size - context->offset;

        if(hls_m3u8_eof)
        {
            return 0;
        }
        
	if (copy_size > left) {
		copy_size = left;
	}

	if (0 < copy_size) {
		memcpy(context->buf + context->offset, ptr, copy_size);
		context->offset += copy_size;
	}

	LOG("copy_size: %d\n", copy_size);
	return copy_size;
}

static int http_download(const char *url, uint8_t *buf, uint32_t size)
{
    CURL *curl = NULL;
    COMMON_DOWNLOAD_CONTEXT context;
    int retval = 0;

    memset(&context, 0x0, sizeof (context));
    context.buf = buf;
    context.size = size;
    context.offset = 0;

    curl = curl_easy_init( );
    if (NULL == curl) {
        LOG("curl easy init fail\n");
        return -1;
    }

    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(curl, CURLOPT_URL, url);
    //curl_easy_setopt(curl, CURLOPT_TIMEOUT, HLS_NETWORK_TIMEOUT);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, HLS_NETWORK_CONN_TIMEOUT);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, common_download_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &context);

    curl_easy_perform(curl);
    curl_easy_cleanup(curl);
    return retval;
}

static uint32_t http_get_content_length(CURL *curl)
{
    uint32_t content_length = 0;

    curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, (double *)&content_length);
    return content_length;
}

#ifdef DSC_SUPPORT
static int decrypt_then_write_to_ring_buffer(HLSContext *context)
{
    aes_cbc_decrypt(context->dec_handle, 
    		context->enc_buf, 
    		context->dec_buf, 
    		context->enc_len, 
    		context->ivec);
#ifdef IS_RECORD_FILE
    if (NULL != context->stream)
    {
        fwrite(context->dec_buf, 1, context->enc_len, context->stream);
        fflush(context->stream);
    }
#endif
    if (context->enc_len != ring_buffer_write_complete(context, context->dec_buf, context->enc_len))
    {
        return -1;
    }
    return 0;
}

static size_t write_encrypted_data_to_ring_buffer(HLSContext *context, char *ptr, size_t size, size_t nmemb)
{
    uint32_t content_length = 0;
    int total = size * nmemb;
    int cpy_len = HLS_DEC_BUF_SIZE - context->enc_len;

    cpy_len = cpy_len < total ? cpy_len : total;
    memcpy(context->enc_buf + context->enc_len, ptr, cpy_len);
    context->enc_len += cpy_len;

    if (context->enc_len == HLS_DEC_BUF_SIZE)
    {
        if (-1 == decrypt_then_write_to_ring_buffer(context))
        {
            LOG("dec then write rb fail<1>\n");
            return 0;
        }

        context->enc_len = 0;
        
        if (cpy_len < total)
        {
            context->enc_len = total - cpy_len;
            memcpy(context->enc_buf, ptr + cpy_len, context->enc_len);			
        }
    }

    context->cur_size += total;
    content_length = http_get_content_length(context->curl_ts);

    if (context->cur_size == content_length && 0 < context->enc_len)
    {
        context->enc_len = context->enc_len / HLS_AES_BLOCK_SIZE * HLS_AES_BLOCK_SIZE;
        if (-1 == decrypt_then_write_to_ring_buffer(context))
        {
            LOG("dec then write rb fail<2>\n");
            return 0;
        }
        context->enc_len = 0;
    }

    return total;
}
#endif

//The maximum amount of body data that can be passed to the write callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE (the usual default is 16K).
static size_t transport_stream_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{	
    HLSContext *context = (HLSContext *)userdata;	

    // force the perform thread return ASAP
    if (context->is_do_seek)
    {
        return 0;
    }
#ifdef DSC_SUPPORT
    if (context->is_encryped)
    {
        return write_encrypted_data_to_ring_buffer(context, ptr, size, nmemb);
    }
    else
#endif        
    {
    #ifdef IS_RECORD_FILE
        if (NULL != context->stream)
        {
            fwrite(ptr, 1, size * nmemb, context->stream);
            fflush(context->stream);
        }
    #endif
        return ring_buffer_write_complete(context, ptr, size * nmemb);
    }
}

static int download_ts_segment(HLSContext *context, const char *segment)
{
    char *old_path = NULL;
    char new_path[HTTP_COMMON_LINE_LENGTH] = {0};

    curl_easy_getinfo(context->curl, CURLINFO_EFFECTIVE_URL, &old_path);
    ff_make_absolute_url(new_path, HTTP_COMMON_LINE_LENGTH, old_path, segment);
    curl_easy_setopt(context->curl_ts, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(context->curl_ts, CURLOPT_URL, new_path);
    curl_easy_setopt(context->curl_ts, CURLOPT_WRITEFUNCTION, transport_stream_callback);
    curl_easy_setopt(context->curl_ts, CURLOPT_PROGRESSFUNCTION, curl_progress_cb);
    //curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, (void*)tpRsrc);
    curl_easy_setopt(context->curl_ts, CURLOPT_NOPROGRESS, 0);

    curl_easy_setopt(context->curl_ts, CURLOPT_WRITEDATA, context);

    context->cur_size = 0;
    return curl_easy_perform(context->curl_ts);
}

static int download_key(HLSContext *context, const char *segment)
{
    char *old_path = NULL;
    char new_path[HTTP_COMMON_LINE_LENGTH] = {0};

    curl_easy_getinfo(context->curl, CURLINFO_EFFECTIVE_URL, &old_path);
    ff_make_absolute_url(new_path, HTTP_COMMON_LINE_LENGTH, old_path, segment);

    return http_download(new_path, context->key, 16);
}

#if 0
static int ff_get_line(const char *stream, char *buf, int maxlen)
{
    int i = 0;
    char c = '\0';

    c = stream[0];
    while(('\n' != c) && c)
    {
        if(i>= maxlen)
        {
            LOG("###Error line length:%s\n", buf);
            return -1;
        }
        c = stream[i];
        buf[i++] = c;
    }
    return i;
}

#else
static int ff_get_line(const char *stream, char *buf, int maxlen)
{
    int i = 0;
    char c = '\0';

    do
    {
        c = stream[i];
        if (c && i < maxlen-1)
        {
            buf[i++] = c;
        }
        else if (i >= (maxlen-1))
        {
            return -1;
        }
    } while (c != '\n' && c);

    buf[i] = 0;
    return i;
}
#endif

static int read_chomp_line(const char *stream, char *buf, int maxlen)
{
    int len = 0;
    int valid_len = 0;

    len = ff_get_line(stream, buf, maxlen);
    if(len<0)
    {
        return -1;
    }

    valid_len = len;
    while (valid_len > 0 && isspace(buf[valid_len - 1]))
    {
        buf[--valid_len] = '\0';
    }
    //LOG("valid length:%d\n",valid_len);
    return len;
}

static void hls_download(HLSContext *content);

#ifdef DSC_SUPPORT
//#EXT-X-KEY:METHOD=AES-128,URI="test.key",IV=0x30313032303330343035303630373038
static int update_key_info(HLSContext *context, const char *key_line)
{
    char *str_start = NULL;
    char key_path[HTTP_COMMON_LINE_LENGTH] = {0};

    context->is_encryped = 1;// consider the case: METHOD=NONE
    sscanf(key_line, "%*[^\"]\"%[^\"]", key_path);
    LOG("key path: %s\n", key_path);

    if (-1 == download_key(context, key_path))
    {
        LOG("download key file fail\n");
        return -1;
    }
    if ((str_start = strstr(key_line, "IV=0x")) != NULL ||
            (str_start = strstr(key_line, "IV=0X")) != NULL)
    {
        context->has_ivec = 1;
        ff_hex_to_data(context->ivec, str_start + 5);
        LOG("key: %.16s\n", context->key);
        LOG("ivec: %.16s\n", context->ivec);
    }

    return 0;
}
#endif

static int download_transport_stream_once(HLSContext *context)
{
	char cur_line[HTTP_COMMON_LINE_LENGTH]={0};
	int line_length = 0;     
    static int cnt = 0;

    if(context->exit_flag)
    {
        return -1;
    }
    
    MutexLock(&context->lock);

    if(NULL == context->m3u8_stream)
    {
        MutexUnlock(&context->lock);            
        return 0;
    }
    
    line_length = read_chomp_line(context->m3u8_stream + context->cur_pos, cur_line, HTTP_COMMON_LINE_LENGTH);

    if(0 > line_length)
    {
        hls_m3u8_eof = 1;
        MutexUnlock(&context->lock);            
        return -1;
    }

    if ((0 == line_length))
    {
        if (cnt ++ > 100)
        {
            cnt = 0;
            LOG("wait downding ts...\n");
        }
        osal_task_sleep(10);
        MutexUnlock(&context->lock);            
        return 0;
	}

	LOG("line length: %d, cur line: %s\n", line_length, cur_line);
        context->cur_pos += line_length;

        if (NULL != strstr(cur_line, "#EXTINF"))
    {
        context->is_segment = 1;
    }
    else if (NULL != strstr(cur_line, "#"))
    {
        LOG("comment line, it's ignored, line length: %d\n", line_length);
    }
    else if (cur_line[0])
    {		
        if (context->is_segment)
        {
            context->is_segment = 0;
        #ifdef DSC_SUPPORT
            if (context->is_encryped)
            {
                if (context->segments[context->segment_idx].key_pos != context->cur_key_pos)
                {
                    char key_line[HTTP_COMMON_LINE_LENGTH] = {0};
                    int line_length = 0;

                    // update the key pos
                    LOG("update key info, cur key pos: %d, seg key pos: %d\n", context->cur_key_pos,
                                context->segments[context->segment_idx].key_pos);

                    context->cur_key_pos = context->segments[context->segment_idx].key_pos;

                    line_length = read_chomp_line(context->m3u8_stream + context->segments[context->segment_idx].key_pos, 
                        key_line, HTTP_COMMON_LINE_LENGTH);
                    if(0 >= line_length)
                    {
                        hls_m3u8_eof = 1;
                        MutexUnlock(&context->lock);	
                        return -1;
                    }

                    if (NULL != strstr(key_line, "#EXT-X-KEY:"))
                    {
                        if(NULL != strstr(key_line, "METHOD=NONE"))
                        {
                            context->is_encryped = 0;
                        }
                        else
                        {
                            if (-1 == update_key_info(context, key_line))
                            {
                                LOG("update key info fail\n");
                                MutexUnlock(&context->lock); 
                                hls_m3u8_eof = 1;
                                return -1;
                            } 
                        }
                    }
                    else
                    {
                        MutexUnlock(&context->lock); 
                        hls_m3u8_eof = 1;
                        return -1;
                    }
                }
                
                if (NULL != context->dec_handle)
                {
                    aes_cbc_destroy(context->dec_handle);
                }
                if ((context->dec_handle = aes_cbc_init(context->key, 128)) == NULL)
                {
                    LOG("aes cbc init fail\n");
                    MutexUnlock(&context->lock); 
                    hls_m3u8_eof = 1;
                    return -1;
                }
                if (!context->has_ivec)
                {
                    memset(context->ivec, 0x0, sizeof (*context->ivec));
                    *((int *)(context->ivec + 12)) = htonl(context->segments[context->segment_idx].seq_no);
                }
            }
        #endif
            LOG("Now download_seq_no:%d\n",context->download_seq_no);
            context->download_seq_no ++;
            
            MutexUnlock(&context->lock);            
            if ((0 != download_ts_segment(context, cur_line)) && (!context->is_do_seek))
            {
                hls_m3u8_eof = 1;
                LOG("###Download error, cur_line:[%s]\n",cur_line);
                return -1;
            }
            MutexLock(&context->lock);
            
            if((0 == context->is_continue) && (context->download_seq_no == context->segments[context->segment_nr -1].seq_no))
            {
                hls_m3u8_eof = 1;
            }
            ++context->segment_idx;
        }			
    }
    else
    {
        LOG("not proc line, it's ignored\n");			
    }
    
    MutexUnlock(&context->lock);            
	return 0;
}

static void download_transport_stream(HLSContext *context)
{
    while(!context->exit_flag)
    {
        if (context->is_do_seek)
        {
            context->is_do_seek = 0;
            // reset the ring, speed up the seek operation
            //ring_buffer_reset(context->rb);
        }
        if (-1 == download_transport_stream_once(context))
        {
            break;
        }
    }
    LOG("did finish ts stream download...\n");
}

static char *download_m3u8_stream(HLSContext *context)
{
    char *content_type = NULL;
    UINT32 content_length = 0;
    COMMON_DOWNLOAD_CONTEXT download_context;

    memset(&download_context, 0x0, sizeof (download_context));

    curl_easy_setopt(context->curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(context->curl, CURLOPT_NOBODY, 1);
    if (0 != curl_easy_perform(context->curl))
    {
        LOG("perform return fail\n");
        return NULL;
    }

    curl_easy_getinfo(context->curl, CURLINFO_CONTENT_TYPE, &content_type);
    content_length = http_get_content_length(context->curl);

    LOG("content type: %s\n", content_type);
    LOG("content length: %u\n", content_length);

    if ((-1 == content_length) || (HLS_M3U8FILE_MAX_LENGTH < content_length))
    {
        LOG("invalid content length\n");
        return NULL;
    }

    char *m3u8_stream = (char *)malloc(content_length + 5);//ensure the file end check not overflow
    if (NULL == m3u8_stream)
    {
        LOG("OOM: alloc m3u8 buf fail\n");
        return NULL;
    }
    memset(m3u8_stream, 0x0, content_length + 1);

    download_context.buf = m3u8_stream;
    download_context.size = content_length;
    download_context.offset = 0;

    curl_easy_setopt(context->curl, CURLOPT_NOBODY, 0);
    curl_easy_setopt(context->curl, CURLOPT_WRITEFUNCTION, common_download_callback);
    curl_easy_setopt(context->curl, CURLOPT_PROGRESSFUNCTION, curl_progress_cb);
    //curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, (void*)tpRsrc);
    curl_easy_setopt(context->curl, CURLOPT_NOPROGRESS, 0);

    curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, &download_context);
    if (0 != curl_easy_perform(context->curl))
    {
        free(m3u8_stream);
        return NULL;
    }
    return m3u8_stream;
}


BOOL set_user_choice(int num)
{
    BOOL choice_change = FALSE;
    if(num != user_choice)
    {
        choice_change = TRUE;
    }
    user_choice = num;
    return choice_change; 
}

static int variants_choice(HLSContext *context, char *m3u8_stream)
{
    char cur_line[HTTP_COMMON_LINE_LENGTH] = {0};
    char *old_path = NULL;
    char new_path[HTTP_COMMON_LINE_LENGTH] = {0};
    int user_choice_num = 0;
    int line_length = 0;

    //!app should notify the user choice
    //user_choice_num = ap_notify_user_choice_cb();
    if(set_user_choice(user_choice_num))
    {
        line_length = read_chomp_line(m3u8_stream + context->variants[user_choice_num].url_pos, cur_line, HTTP_COMMON_LINE_LENGTH);

        if(0 > line_length)
        {
            LOG("###Error,illegal length!\n");
            return -1;
        }
        
        if(strstr(cur_line, "://"))
        {
            curl_easy_setopt(context->curl, CURLOPT_URL, cur_line);
        }
        else
        {
            curl_easy_getinfo(context->curl, CURLINFO_EFFECTIVE_URL, &old_path);
            ff_make_absolute_url(new_path, HTTP_COMMON_LINE_LENGTH, old_path, cur_line);
            curl_easy_setopt(context->curl, CURLOPT_URL, new_path);
        }
    }
    else
    {
        //LOG("user choice no changing!\n");
    }
    return 0;
}

static void hls_m3u8_process(HLSContext *context)
{
    int reload_interval = 0;
    int i = 0;

    while((!context->exit_flag) && (!hls_m3u8_eof))
    {
//!download m3u8 file    
        char *m3u8_stream = download_m3u8_stream(context);

        MutexLock(&context->lock);
        if (NULL == m3u8_stream) 
        {
            LOG("*****download_m3u8_stream error!\n");
            MutexUnlock(&context->lock);
            hls_m3u8_eof = 1;
            break;
        }

//!parse new m3u8 file        
        if (-1 == parse_playlist(context, m3u8_stream)) 
        {
            LOG("*****parse_playlist error!\n");
            free(m3u8_stream);
            MutexUnlock(&context->lock);
            hls_m3u8_eof = 1;
            break;
        }

        if(context->variant_nr)
        {
            if(0 != variants_choice(context,m3u8_stream))//check user choice for variants source
            {
                hls_m3u8_eof = 1;
                break;
            }
            MutexUnlock(&context->lock);
            continue;
        }

        context->m3u8_stream = m3u8_stream;//save this m3u8 file address
       
        MutexUnlock(&context->lock);
        
        if(!context->is_continue)
        {
            break;
        }
        
//!wait for next m3u8 download
        if(context->is_new_seqno)
        {
            reload_interval = context->segment_nr > 0 ? 
            context->segments[context->segment_nr - 1].duration : context->target_duration;
        }
        else
        {
            reload_interval = context->target_duration / 2;
        }
        
        for(i=0; i<reload_interval*100; i++)
        {
            
            if(context->exit_flag)
            {
                return;
            }
            osal_task_sleep(10);
        }

    }
}

static void hls_ts_segments_download(HLSContext *context)
{
    download_transport_stream(context);
    free(context->m3u8_stream);
}

//For: m3u8 file download and parse
static void *hls_m3u8_parse_thread(void *param)
{
    HLSContext *context = (HLSContext *)param;  
    hls_m3u8_process(context);
    MutexLock(&context->lock);
    context->tid_m3u8_exit = 1;
    MutexUnlock(&context->lock);
    LOG("###hls_m3u8_parse_thread exit!\n");
    return (void *)0;
}

//For: ts download and seek operation 
static void *hls_ts_download_thread(void *param)
{
    HLSContext *context = (HLSContext *)param;
    hls_ts_segments_download(context);
    MutexLock(&context->lock);
    context->tid_ts_exit = 1; 
    MutexUnlock(&context->lock);
    LOG("###hls_ts_download_thread exit!\n");
    return (void *)0;
}

static void cleanup(HLSContext *context)
{
    if (NULL != context)
    {
        MutexDestroy(&context->lock);
        rbuffer_destroy(&context->rb);

        if (NULL != context->curl)
        {
            curl_easy_cleanup(context->curl);
        }
        if (NULL != context->dec_handle)
        {
            aes_cbc_destroy(context->dec_handle);
        }
        LOG("...cleanup\n");
    #ifdef IS_RECORD_FILE
        if (NULL != context->stream)
        {
            LOG("close record file\n");
            fclose(context->stream);
            //fsync("/mnt/uda1/ALIDVRS2");
            fsync("/mnt/uda1");
            _fsync_root("/mnt/uda1/ALIDVRS2");
            //fs_unmount("/mnt/uda1", 1);
        }
    #endif
        free(context);
    }
}

static int playlist_overview(HLSContext *context, char *m3u8_stream,int * sq_num)
{
    char cur_line[HTTP_COMMON_LINE_LENGTH] = {0};
    int offset = 0;
    int line_length = 0;
    double duration = 0;
    int start_seq_no = 0;
    char *var_bandwidth = NULL;

    LOG("m3u8:\n%s\n", m3u8_stream);
    if(!is_m3u8_format(m3u8_stream))
    {
        return -1;
    }
//!get first sequence num.
    for (offset=0; ; offset+=line_length)
    {
        line_length = read_chomp_line(m3u8_stream + offset, cur_line, HTTP_COMMON_LINE_LENGTH);

        if (0 > line_length)
        {
            LOG("###Error,illegal length!\n");
            return -1;
        }
        if (0 == line_length)
        {
            break;
        }

        if (NULL != strstr(cur_line, "#EXT-X-MEDIA-SEQUENCE:"))
        {
            sscanf(cur_line, "%*[^:]:%d", &start_seq_no);
            break;
        }
    }
    *sq_num = start_seq_no;
        
//!check basic infor
    for (offset=0; ; offset+=line_length)
    {
        line_length = read_chomp_line(m3u8_stream + offset, cur_line, HTTP_COMMON_LINE_LENGTH);

        if(0 > line_length)
        {
            LOG("###Error,illegal length!\n");
            return -1;
        }
        if (0 == line_length)
        {
            break;
        }

        if (NULL != strstr(cur_line, "#EXTINF:"))
        {
            sscanf(cur_line, "%*[^:]:%lf", &duration);
            context->duration += duration;
            ++context->segment_nr;
        }
        else if(NULL != strstr(cur_line, "#EXT-X-STREAM-INF:"))
        {
            ++context->variant_nr;
        }
        else if (NULL != strstr(cur_line, "#EXT-X-ENDLIST"))
        {
            context->is_continue = 0;
            LOG("###VOD m3u8:%d\n",context->is_continue);
            break;
        }
    }

//!alloc memory for variants,segments infor saving
    if(context->segment_nr)
    {
        context->segments = (segment *)malloc(sizeof (segment) * context->segment_nr);
        if (NULL == context->segments)
        {
            LOG("malloc segments fail\n");
            return -1;
        }
        memset(context->segments, 0x0, sizeof (segment) * context->segment_nr);
    }
    if(context->variant_nr)
    {
        context->variants = (segment *)malloc(sizeof (variants) * context->variant_nr);
        if (NULL == context->variants)
        {
            LOG("malloc segments fail\n");
            return -1;
        }
        memset(context->variants, 0x0, sizeof (variants) * context->variant_nr);
    }
    
}

static int parse_playlist(HLSContext *context, char *m3u8_stream)
{
    char cur_line[HTTP_COMMON_LINE_LENGTH] = {0};
    int offset = 0;
    int line_length = 0;
    int start_seq_no = 0;//jary test
    char *var_bandwidth = NULL;
    int key_pos = 0;
    int i = 0;
    int j = 0;
    
//!free or reset old data
    if(context->m3u8_stream)
    {
        free(context->m3u8_stream);
    }
    if(context->segments)
    {
        free(context->segments);
    }
    if(context->variants)
    {
        free(context->variants);
    }
    
    context->m3u8_stream = NULL;
    context->segments = NULL;
    context->segment_nr = 0;
    context->duration = 0;
    context->variants = NULL;
    context->variant_nr = 0;
    context->is_continue = 1;

//!overview the new m3u8    
    if(-1 == playlist_overview(context,m3u8_stream,&start_seq_no))
    {
        return -1;
    }

//!get start sequence number and check if there any changes  
    if(start_seq_no != context->start_seq_no)
    {
        context->is_new_seqno = 1;//!it's a new m3u8 file
        context->start_seq_no = start_seq_no;
    }
    else
    {
        context->is_new_seqno = 0;
    }
       
    LOG("start squence no:%d\n", context->start_seq_no);
    LOG("duration: %lf\n", context->duration);
    LOG("segment number: %d\n", context->segment_nr);

//!parse other's infor,and save variants/segments infor  
    for (offset=0; ; offset+=line_length)
    {
        line_length = read_chomp_line(m3u8_stream + offset, cur_line, HTTP_COMMON_LINE_LENGTH);

        if(0 > line_length)
        {
            LOG("###Error,illegal length!\n");
            return -1;
        }
        if (0 == line_length)
        {
            break;
        }

        if (NULL != strstr(cur_line, "#EXT-X-VERSION"))
        {
            //not process
        }
        else if (NULL != strstr(cur_line, "#EXT-X-PLAYLIST-TYPE"))
        {
            //not process
        }
        else if (NULL != strstr(cur_line, "#EXT-X-STREAM-INF:"))
        {
            LOG("multi-rate m3u8 file\n");
            var_bandwidth = strstr(cur_line, "BANDWIDTH=");
            if(NULL != var_bandwidth)
            {
                context->variants[j].bandwidth = ATOI(var_bandwidth+10);
            }
            else
            {
                LOG("error,no bandwidth infor!\n");
            }
            context->variants[j].url_pos = offset+line_length;
            j++;           
        }
        else if (NULL != strstr(cur_line, "#EXT-X-I-FRAME-STREAM-INF:"))
        {
            //not process...
        }
        else if(NULL != strstr(cur_line, "#EXT-X-DISCONTINUITY"))
        {
            //not process...
        }
        else if (NULL != strstr(cur_line, "#EXT-X-PROGRAM-DATA-TIME"))
        {
            //not process
        }
        else if (NULL != strstr(cur_line, "#EXT-X-KEY:"))
        {
            key_pos = offset;
        #ifdef DSC_SUPPORT
            context->cur_key_pos = offset;
        #endif
            if(NULL != strstr(cur_line, "METHOD=NONE"))
            {
                continue;
            }
        #ifdef DSC_SUPPORT
            context->cur_key_pos = offset;
            if (-1 == update_key_info(context, cur_line))
            {
                LOG("update key info fail\n");
                return -1;
            }
        #endif
        }
        else if (NULL != strstr(cur_line, "#EXT-X-TARGETDURATION:"))
        {
            sscanf(cur_line, "%*[^:]:%d", &context->target_duration);
        }
        else if (NULL != strstr(cur_line, "#EXTINF:"))
        {
            sscanf(cur_line, "%*[^:]:%lf", &context->segments[i].duration);
            context->segments[i].pos = offset;
            context->segments[i].key_pos = key_pos;
            context->segments[i].seq_no = start_seq_no++;
            ++i;
        }
        else if (NULL != strstr(cur_line, "#"))
        {
            LOG("comment line, ignored:[%s]\n", cur_line);
        }
        else
        {
            //LOG("Not Proc:[%s]\n",cur_line);
        }
    }

//!first download postion of segments in live play, should be one of the last 3 segments in m3u8 file 
    if ((0 == context->download_seq_no) && (context->is_continue))
    {
        if(context->segment_nr >= 3)
        {
            context->download_seq_no = context->start_seq_no + context->segment_nr - 3;
            context->cur_pos = context->segments[context->segment_nr - 3].pos;
        }
        else
        {
            context->download_seq_no = context->start_seq_no;
        }
        LOG("first download_seq_no:[%d]\n",context->download_seq_no);
    }

//!get the download position for the update m3u8
    for(i= 0; i< context->segment_nr; i++)
    {
        if(((context->segments[i].seq_no) >= (context->download_seq_no)) && (0 !=context->download_seq_no))
        {
            context->cur_pos = context->segments[i].pos;
            context->download_seq_no = context->segments[i].seq_no;
            LOG("next download_seq_no:[%d]\n",context->download_seq_no);
            break;
        }
    }
            
    return 0;
}

#ifndef __linux
static FILE *hls_fopen_ex(const char *path, uint8_t *buf, uint32_t size, uint32_t offset)
{
    return (FILE *)hls_stream_open(path);
}

static size_t hls_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return hls_stream_read((HLSContext *)stream, ptr, size * nmemb);
}

static int hls_fseek(FILE *stream, long long offset, int whence)
{
    LOG("hls_fseek invoked... %lld\n", offset);
    if (1 == offset)
    {
    	__hls_seek_forward(10);
    }
    else
    {
    	__hls_seek_backward(10);
    }
    return 0;
}

int hls_file_eof(FILE * m3u8file) //jary
{
    return hls_m3u8_eof;
}

/* static long hls_ftell(FILE *stream); */
/* static void hls_rewind(FILE *stream); */

static void hls_fclose(FILE *stream)
{
    hls_stream_close((HLSContext *)stream);	      
}

void hls_init_pe_cache_cb_fp(pe_cache_cb_fp *fp)
{
    memset(fp, 0x0, sizeof (*fp));
    fp->file_open = hls_fopen_ex;
    fp->file_read = hls_fread;
    fp->file_close = hls_fclose;
    fp->file_eof = hls_file_eof; 
    //fp->file_seek = hls_fseek;
    fp->file_seek = NULL; //jary test
}
#endif

static int is_m3u8_format(const char *m3u8_stream)
{
    /* Require #EXTM3U at the start, and either one of the ones below
    * somewhere for a proper match. */
    if (strncmp(m3u8_stream, "#EXTM3U", 7))
    {
        return 0;
    }
    if (strstr(m3u8_stream, "#EXT-X-STREAM-INF:") ||strstr(m3u8_stream, "#EXT-X-TARGETDURATION:")
        ||strstr(m3u8_stream, "#EXT-X-MEDIA-SEQUENCE:"))
    {
        return 1;
    }
    return 0;
}


int is_http_live_stream(const char *url)
{
    LOG("HLS Module Version: %s\n", "aliVersion1");

    char probe_buf[HLS_PROBE_BUF_SIZE] = {0};
    int ret = 0;

    if (-1 == http_download(url, probe_buf, HLS_PROBE_BUF_SIZE - 1)) {
        LOG("m3u8 download fail\n");
        return 0;
    }

    LOG("m3u8:\n%s\n", probe_buf);
    ret = is_m3u8_format(probe_buf);
    m_is_hls = ret;
    LOG("is_m3u8_format:%d\n", ret);
    
    return ret;
}

int http_is_hls(void)
{
    return m_is_hls;
}

void http_set_hls(int hls)
{
    m_is_hls = hls;
}


HLSContext *hls_stream_open(const char *path)
{
    hls_m3u8_eof = 0;

    HLSContext *context = NULL;

    context = (HLSContext *)malloc(sizeof (*context));
    if (NULL == context) {
        LOG("alloc hls context fail\n");
        return NULL;
    }

    memset(context, 0x0, sizeof (*context));

    MutexInit(&context->lock);
    context->curl = curl_easy_init( );
    context->curl_ts = curl_easy_init( );

    if (NULL == context->curl) {
        goto cleanup;
    }

    curl_easy_setopt(context->curl, CURLOPT_FOLLOWLOCATION, 1);//jary
    curl_easy_setopt(context->curl, CURLOPT_URL, path);
    curl_easy_setopt(context->curl, CURLOPT_TIMEOUT, HLS_NETWORK_TIMEOUT);
    curl_easy_setopt(context->curl, CURLOPT_CONNECTTIMEOUT, HLS_NETWORK_CONN_TIMEOUT);

    //!step1:create m3u8 file parse thread
    context->tid_m3u8 = thread_create_joinable(hls_m3u8_parse_thread, context);
    if (NULL_THREAD == (context->tid_m3u8))
    {
        LOG("***hls_m3u8_parse_thread create fail\n");
        goto cleanup;
    }

    //!step2:create ts segments download thread
    if (0 == rbuffer_init(&context->rb, NULL, HLS_RING_BUF_SIZE))
    {
        goto cleanup;
    }

    context->tid_ts = thread_create_joinable(hls_ts_download_thread, context);
    if (NULL_THREAD == (context->tid_ts))
    {
        LOG("***hls_ts_download_thread create fail\n");
        goto cleanup;
    }

    // for test purpose
    s_cur_pos = 0;
    m_context = context;

    return context;

cleanup:
    cleanup(context);
    return NULL;	
}

int hls_stream_read(HLSContext *context, unsigned char *buf, unsigned int size)
{
    return ring_buffer_read_complete(context, buf, size);
}

void hls_stream_close(HLSContext *context)
{
    hls_m3u8_eof = 1;
    MutexLock(&context->lock);
    context->exit_flag = 1;
    LOG("###exit_flag\n");
    MutexUnlock(&context->lock);
    user_choice = 0xFFFFFFFF;

    while((!context->tid_m3u8_exit)||(!context->tid_ts_exit))
    {
        osal_task_sleep(10);
    }
        
    //LOG("will join\n");
    thread_join(context->tid_m3u8);

    thread_join(context->tid_ts);

    //LOG("will clean\n");

    cleanup(context);

    //LOG("did clean\n");
    hls_m3u8_eof = 0;
    m_context = NULL;
}

long hls_stream_duration(void)
{
    if (NULL == m_context) {
    	return -1;
    }

    return (long)m_context->duration;
}

int hls_stream_seek(HLSContext *context, long time)
{
    int i;
    double sum;

    if (NULL == context) {
    	return -1;
    }

    MutexLock(&context->lock);

    context->is_do_seek = 1;

    //ring_buffer_reset(context->rb);
    rbuffer_clean(&context->rb);//jary

    for (i=0, sum=0.0; i<context->segment_nr; i++)
    {
        sum += context->segments[i].duration;
        if (sum > time)
        {
            break;
        }
    }

    if (i == context->segment_nr) {
    	--i;
    }

    LOG("seek i: %d\n", i);

    context->segment_idx = i; 
    context->cur_pos = context->segments[context->segment_idx].pos;

#ifdef DSC_SUPPORT
    if (context->segments[context->segment_idx].key_pos != context->cur_key_pos)
    {
        char cur_line[HTTP_COMMON_LINE_LENGTH];
        int line_length;

        // update the key pos
        LOG("update key info, cur key pos: %d, seg key pos: %d\n", context->cur_key_pos,
            context->segments[context->segment_idx].key_pos);

        context->cur_key_pos = context->segments[context->segment_idx].key_pos;

        line_length = read_chomp_line(context->m3u8_stream + context->cur_pos, cur_line, HTTP_COMMON_LINE_LENGTH);

        if(0 > line_length)
        {
            return -1;
        }
        if (0 == line_length) 
        {		
            MutexUnlock(&context->lock);	
            return -1;
        }

        if (-1 == update_key_info(context, cur_line)) {
        	MutexUnlock(&context->lock);
        	return -1;
        }
    }
#endif

    MutexUnlock(&context->lock);
    LOG("did seek\n");

    return 0;
}

#ifndef NDEBUG
#include <signal.h>

#define READ_BUF_SIZE	1024
static char buf[READ_BUF_SIZE];
static int size;
static int downsize = 0;

static void *read_thread(void *param)
{
	HLSContext *context = (HLSContext *)param;

	for (;;) {
		//LOG("read thread, will hls fread\n");
		size = hls_stream_read(context, buf, READ_BUF_SIZE);
		//LOG("read thread, did hls fread\n");

		if (0 == size) {
			LOG("if (0 == size) {\n");
			break;
		}

		fwrite(buf, 1, size, stdout);
		fflush(stdout);

		downsize += size;

		if (size != READ_BUF_SIZE) {
			LOG("if (size != READ_BUF_SIZE) {\n");
			break;
		}
	}

	LOG("read thread, total download size: %d\n", downsize);
	return (void *)0;
}

void hls_test(const char *url)
{
	char c;
	pthread_t tid;
	long seek_step = 10;
	long cur_pos = 0;
	HLSContext *context;
	int whence = SEEK_SET;
	long duration;

	context = hls_stream_open(url);
	if (NULL == context) {
		LOG("hls fopen fail\n");
		return;
	}

	duration = hls_stream_duration();

	pthread_create(&tid, NULL, read_thread, context);

	for (;;) {
		c = getchar();
		if ('q' == c || 'Q' == c) {
			LOG("will close the hls stream\n");
			hls_stream_close(context);
			LOG("did close the hls stream\n");
			break;
		} else if ('f' == c || 'F' == c) {
			cur_pos += seek_step;
			cur_pos = cur_pos < duration ? cur_pos : duration;
			hls_stream_seek(context, cur_pos);
		} else if ('b' == c || 'B' == c) {
			cur_pos -= seek_step;
			cur_pos = cur_pos > 0 ? cur_pos : 0;
			hls_stream_seek(context, cur_pos);
		}
	}
}
#endif



#endif
